/*
 * @(#)BeanPointNoticeImpl.java   1.0  2022年3月10日
 * 
 * Copyright (c)	2014-2020. All Rights Reserved.	GuangZhou hhmk Technology Company LTD.
 */
package com.swift.monitor.core.notice.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.swift.core.cloud.NoDstb;
import com.swift.monitor.core.alarm.AlarmNotice;
import com.swift.monitor.core.notice.AbstractMonitorNotice;
import com.swift.monitor.core.notice.BeanMonitorNotice;
import com.swift.monitor.core.point.AlarmLevel;
import com.swift.monitor.core.point.BeanAlarmPoint;
import com.swift.monitor.core.point.BeanCountListPoint;
import com.swift.monitor.core.point.BeanCountPoint;
import com.swift.monitor.core.point.BeanPoint;
import com.swift.util.bean.BeanUtil;
import com.swift.util.exec.ThreadUtil;
import com.swift.util.type.TypeUtil;

/**
 * 添加说明 
 * @author zhengjiajin
 * @version 1.0 2022年3月10日
 */
@Component
public class BeanPointNoticeImpl extends AbstractMonitorNotice<BeanPoint> implements BeanMonitorNotice {

    @Autowired
    private List<AlarmNotice<BeanAlarmPoint>> beanAlarmPointList;
    
    @Autowired
    private List<AlarmNotice<BeanCountPoint>> beanCountPointList;
    
    @Autowired
    private List<AlarmNotice<BeanCountListPoint>> beanCountListPointList;
    //通过线程ID来关联前后方法
    private Map<Long,List<BeanPoint>> threadBeanMap = new ConcurrentHashMap<>();
    //统计
    private Map<String,BeanCountPoint> countBeanMap = new ConcurrentHashMap<>();
    
    private void doBeanCount(BeanPoint t) {
        String key = countKey(t).intern();
        synchronized (key) {
            if(!countBeanMap.containsKey(key)) {
                countBeanMap.put(key, new BeanCountPoint());
            }
            BeanCountPoint point = countBeanMap.get(key);
            point.setSysId(t.getSysId());
            point.setClassName(t.getClassName());
            point.setMethedName(t.getMethedName());
            point.setTotalTime(point.getTotalTime()+t.getExecTime());
            point.setCountNum(point.getCountNum()+1);
            point.setAveTime(point.getTotalTime()/point.getCountNum());
            if(point.getMaxTime()<t.getExecTime()) point.setMaxTime(t.getExecTime());
            if(t.getEx()!=null) point.setThrowNum(point.getThrowNum()+1);
        }
    }
    /**
     * 高并发时,可能会丢部份新类
     * @return
     */
    private Collection<BeanCountPoint> clearCountBeanMap(){
        Collection<BeanCountPoint> list = countBeanMap.values();
        List<BeanCountPoint> cloneList = new ArrayList<>(list);
        countBeanMap.clear();
        return cloneList;
    }
    
    @NoDstb
    @Scheduled(cron="0 0/15 * * * ?")
    public void runBeanCount() {
        Date pointTime = new Date();
        Collection<BeanCountPoint> list =clearCountBeanMap();
        if(TypeUtil.isNull(list)) return;
        long totalNum = getCountNum(list);
        BeanCountListPoint listPoint = new BeanCountListPoint();
        for(BeanCountPoint point : list) {
            point.setPointTime(pointTime);
            
            if(point.getAveTime()>5) {
                point.setAlarmInfo(point.getAlarmInfo()+" AveTime is too long ");
                point.setLevel(AlarmLevel.WARN);
            }
            
            if(point.getMaxTime()>15) {
                point.setAlarmInfo(point.getAlarmInfo()+" MaxTime is too long ");
                point.setLevel(AlarmLevel.WARN);
            }
            
            if(point.getThrowNum()>0) {
                point.setAlarmInfo(point.getAlarmInfo()+" exception in method ");
                point.setLevel(AlarmLevel.ERROR);
            }
            
            point.setRatio((double)point.getCountNum()/(double)totalNum);
            if(point.getRatio()>0.02) {
                listPoint.getCountPoint().add(point);
            }
            for(AlarmNotice<BeanCountPoint> alarmNotice:beanCountPointList) {
                alarmNotice.alarm(point);
            }
        }
        Collections.sort(listPoint.getCountPoint(), new Comparator<BeanCountPoint>() {

            @Override
            public int compare(BeanCountPoint o1, BeanCountPoint o2) {
                if(o1.getRatio()>o2.getRatio()) {
                    return -1;
                }
                if(o1.getRatio()<o2.getRatio()) {
                    return 1;
                }
                return 0;
            }
            
        });
        
        for(AlarmNotice<BeanCountListPoint> alarmListNotice:beanCountListPointList) {
            alarmListNotice.alarm(listPoint);
        }
    }
    
    private long getCountNum(Collection<BeanCountPoint> list) {
        long total = 0;
        for(BeanCountPoint point : list) {
            total=total+point.getCountNum();
        }
        return total;
    }
    
    private String countKey(BeanPoint t) {
        return t.getClassName()+"."+t.getMethedName();
    }
    
    @PostConstruct
    private void init() {
        new BeanPointThread().start();
    }
    
    /** 
     * @see com.swift.monitor.core.notice.AbstractMonitorNotice#doNotice(com.swift.monitor.core.point.Point)
     */
    @Override
    public void doNotice(BeanPoint t) {
        if(t.getThreadId()==null) return;
        List<BeanPoint> list = threadBeanMap.get(t.getThreadId());
        //判断非别的业务的线程
        runBeanAlarm(list);
        initThreadBeanMap(t.getThreadId());
        threadBeanMap.get(t.getThreadId()).add(t);
    }
    
    private void runBeanAlarm(List<BeanPoint> list) {
        if(TypeUtil.isNull(list)) return;
        BeanPoint firstPoint = list.get(0);
        if(firstPoint.getEndTime()!=null) {
            List<BeanPoint> synList = threadBeanMap.remove(firstPoint.getThreadId());
            if(TypeUtil.isNull(synList)) return;
            if(synList!=list) return;
            //类访问统计分析
            for(BeanPoint t:synList) {
                doBeanCount(t);
            }
            BeanAlarmPoint alarm = new BeanAlarmPoint();
            BeanUtil.copyProperties(alarm, firstPoint);
            alarm.setExecList(synList);
            alarm.setLevel(AlarmLevel.INFO);
            alarm.setAlarmInfo("");
            doThrowable(alarm);
            doExecTime(alarm);
            if(alarm.getLevel().getLevel()>AlarmLevel.INFO.getLevel()) {
                for(AlarmNotice<BeanAlarmPoint> alarmNotice:beanAlarmPointList) {
                    alarmNotice.alarm(alarm);
                }
            }
        }
    }
    
    private void doThrowable(BeanAlarmPoint alarm) {
       if(alarm.getEx()!=null) {
            alarm.setLevel(AlarmLevel.ERROR);
            alarm.setAlarmInfo(alarm.getAlarmInfo()+alarm.getClassName()+"."+alarm.getMethedName()+" exception ");
        }
    }
    
    private void doExecTime(BeanAlarmPoint alarm) {
        //时廷大于5S告警15S异常
        if(alarm.getExecTime()<5*1000) return;
        if(alarm.getExecTime()>=15*1000) {
            alarm.setLevel(AlarmLevel.ERROR);
            alarm.setAlarmInfo(alarm.getAlarmInfo()+alarm.getClassName()+"."+alarm.getMethedName()+" timeout ");
        }
        alarm.setLevel(AlarmLevel.WARN);
        alarm.setAlarmInfo(alarm.getAlarmInfo()+alarm.getClassName()+"."+alarm.getMethedName()+" Execution time too long ");
    }
    
    private void initThreadBeanMap(Long threadId) {
        String lock = TypeUtil.toString(threadId,"").intern();
        synchronized(lock) {
            if(!threadBeanMap.containsKey(threadId)) {
                threadBeanMap.put(threadId, new LinkedList<BeanPoint>());
            }
        }
    }
    
    private class BeanPointThread extends Thread {
        
        public void run() {
            while(true) {
                ThreadUtil.sleep(1000);
                for(List<BeanPoint> list:threadBeanMap.values()) {
                    runBeanAlarm(list);
                }
            }
        }
    }

}
